#!/usr/bin/env python3
"""
JH子系统最简AI聊天服务器
只包含核心聊天功能，完全独立运行
"""

from fastapi import FastAPI, HTTPException, Query, UploadFile, File
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
from typing import Dict, Any, Optional, List
from datetime import datetime
from contextlib import asynccontextmanager
import uvicorn
import sys
import os
from pathlib import Path
import sqlite3
import json
from bs4 import BeautifulSoup
import io
try:
    import PyPDF2
except Exception:
    PyPDF2 = None
try:
    import docx as python_docx
except Exception:
    python_docx = None

# 添加项目根目录到路径
sys.path.insert(0, str(Path(__file__).parent))

# 尝试导入服务
try:
    from src.services.ollama_service import OllamaService, JHChatService
    OLLAMA_AVAILABLE = True
except Exception as e:
    print(f"Ollama服务导入失败: {e}")
    OLLAMA_AVAILABLE = False

try:
    from src.jh_subsystem.job_data_service import job_data_service
    JH_DATA_AVAILABLE = True
except Exception as e:
    print(f"JH数据服务导入失败: {e}")
    JH_DATA_AVAILABLE = False

# Pydantic 模型
class ChatMessage(BaseModel):
    message: str
    context_data: Optional[Dict[str, Any]] = None
    stream: bool = False

class ChatResponse(BaseModel):
    response: str
    timestamp: str
    error: Optional[str] = None

# JobDataFilters 模型
class JobDataFilters(BaseModel):
    job_title: Optional[str] = None
    company_name: Optional[str] = None
    job_location: Optional[str] = None
    job_country: Optional[str] = None
    job_schedule_type: Optional[str] = None
    salary_min: Optional[float] = None
    salary_max: Optional[float] = None
    work_from_home: Optional[bool] = None
    skills: Optional[List[str]] = None

# 数据库初始化函数
async def startup_db():
    """启动时初始化数据库"""
    if JH_DATA_AVAILABLE:
        try:
            print("正在初始化JH数据库...")
            await job_data_service.initialize_database(use_real_data=True)
            print("JH数据库初始化成功!")
        except Exception as e:
            print(f"JH数据库初始化失败: {e}")

@asynccontextmanager
async def lifespan(app: FastAPI):
    # 启动时执行
    await startup_db()
    yield
    # 关闭时执行（如果需要）
    pass

# 创建应用
app = FastAPI(title="JH AI聊天服务", version="1.0.0", lifespan=lifespan)

# CORS中间件
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 静态文件
if os.path.exists("frontend"):
    app.mount("/frontend", StaticFiles(directory="frontend"), name="frontend")

# 全局服务
ollama_service = None
jh_chat_service = None

@app.get("/")
async def root():
    return {
        "message": "JH AI聊天服务",
        "status": "running",
        "ollama_available": OLLAMA_AVAILABLE,
        "jh_data_available": JH_DATA_AVAILABLE
    }

@app.get("/health")
async def health():
    return {"status": "healthy", "timestamp": datetime.now().isoformat()}

# JH数据分析API路由
@app.get("/api/jh/overview")
async def get_overview_statistics():
    """获取概览统计信息"""
    if not JH_DATA_AVAILABLE:
        raise HTTPException(status_code=503, detail="JH数据服务不可用")
    
    try:
        stats = await job_data_service.get_overview_statistics()
        return stats
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取统计信息失败: {str(e)}")

@app.get("/api/jh/jobs")
async def get_jobs(
    page: int = Query(1, ge=1),
    page_size: int = Query(33, ge=1, le=1000),
    job_title: Optional[str] = Query(None),
    company_name: Optional[str] = Query(None),
    job_location: Optional[str] = Query(None),
    job_country: Optional[str] = Query(None),
    job_schedule_type: Optional[str] = Query(None),
    salary_min: Optional[float] = Query(None),
    salary_max: Optional[float] = Query(None),
    work_from_home: Optional[bool] = Query(None),
    skills: Optional[str] = Query(None)
):
    """获取分页职位数据"""
    if not JH_DATA_AVAILABLE:
        raise HTTPException(status_code=503, detail="JH数据服务不可用")
    
    try:
        # 构建筛选条件
        filters = JobDataFilters(
            job_title=job_title,
            company_name=company_name,
            job_location=job_location,
            job_country=job_country,
            job_schedule_type=job_schedule_type,
            salary_min=salary_min,
            salary_max=salary_max,
            work_from_home=work_from_home,
            skills=skills.split(',') if skills else None
        )
        
        result = await job_data_service.get_jobs(page=page, page_size=page_size, filters=filters)
        return result
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取职位数据失败: {str(e)}")

@app.get("/api/jh/stats")
async def get_stats():
    """兼容性统计端点"""
    return await get_overview_statistics()

@app.get("/api/jh/jobs/{job_id}")
async def get_job_detail(job_id: int):
    """获取单个岗位详细信息"""
    if not JH_DATA_AVAILABLE:
        raise HTTPException(status_code=503, detail="JH数据服务不可用")
    
    try:
        job = await job_data_service.get_job_by_id(job_id)
        if not job:
            raise HTTPException(status_code=404, detail="岗位不存在")
        
        return {
            "status": "success",
            "data": job
        }
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取岗位详情失败: {str(e)}")

@app.get("/api/jh/filter-options")
async def get_filter_options():
    """获取筛选选项"""
    if not JH_DATA_AVAILABLE:
        raise HTTPException(status_code=503, detail="JH数据服务不可用")
    
    try:
        return await job_data_service.get_filter_options()
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取筛选选项失败: {str(e)}")

@app.get("/api/jh/chart-data/{chart_type}")
async def get_chart_data(
    chart_type: str,
    dimension: Optional[str] = Query(None),
    limit: Optional[int] = Query(None),
    expanded: bool = Query(False)  # 新增参数，用于区分是否为展开模式
):
    """获取图表数据"""
    if not JH_DATA_AVAILABLE:
        raise HTTPException(status_code=503, detail="JH数据服务不可用")
    
    try:
        # 根据expanded参数决定数据量限制
        if expanded:
            # 展开模式：显示所有数据或更多数据
            if chart_type == "jobTypeChart":
                actual_limit = None  # 不限制
            elif chart_type == "locationChart":
                actual_limit = None  # 不限制
            elif chart_type == "salaryChart":
                actual_limit = None  # 不限制
            elif chart_type == "skillsChart":
                actual_limit = None  # 不限制
            elif chart_type == "companyChart":
                actual_limit = None  # 不限制
            else:
                actual_limit = limit
        else:
            # 正常模式：使用默认限制
            if chart_type == "jobTypeChart":
                actual_limit = limit or 10
            elif chart_type == "locationChart":
                actual_limit = limit or 15
            elif chart_type == "salaryChart":
                actual_limit = limit or 10
            elif chart_type == "skillsChart":
                actual_limit = limit or 20
            elif chart_type == "companyChart":
                actual_limit = limit or 15
            else:
                actual_limit = limit or 10
        
        return await job_data_service.get_chart_data(chart_type, dimension, actual_limit)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取图表数据失败: {str(e)}")

@app.get("/api/jh/chat/status")
async def chat_status():
    """聊天服务状态"""
    if not OLLAMA_AVAILABLE:
        return {
            "ollama_available": False,
            "qwen_model_available": False,
            "error": "Ollama服务不可用",
            "timestamp": datetime.now().isoformat()
        }
    
    try:
        global ollama_service
        if not ollama_service:
            ollama_service = OllamaService()
        
        is_available = await ollama_service.is_available()
        if is_available:
            models = await ollama_service.list_models()
            model_exists = await ollama_service.check_model_exists()
        else:
            models = []
            model_exists = False
        
        return {
            "ollama_available": is_available,
            "qwen_model_available": model_exists,
            "available_models": [m.get("name", "") for m in models],
            "current_model": ollama_service.model_name if ollama_service else "qwen2.5:latest",
            "timestamp": datetime.now().isoformat()
        }
    except Exception as e:
        return {
            "ollama_available": False,
            "qwen_model_available": False,
            "error": str(e),
            "timestamp": datetime.now().isoformat()
        }

@app.post("/api/jh/chat/message")
async def chat_message(chat_data: ChatMessage):
    """处理聊天消息"""
    if not OLLAMA_AVAILABLE:
        raise HTTPException(status_code=503, detail="Ollama服务不可用")
    
    try:
        global ollama_service, jh_chat_service
        
        if not ollama_service:
            ollama_service = OllamaService()
            
        if not await ollama_service.is_available():
            raise HTTPException(status_code=503, detail="Ollama服务不可用")
            
        if not jh_chat_service:
            jh_chat_service = JHChatService(ollama_service)
        
        # 调用聊天服务
        result = await jh_chat_service.chat(
            user_message=chat_data.message,
            context_data=chat_data.context_data or {},
            stream=chat_data.stream
        )
        
        return ChatResponse(**result)
        
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"聊天失败: {str(e)}")

@app.get("/api/jh/chat/suggestions")
async def chat_suggestions():
    """聊天建议"""
    suggestions = [
        {
            "category": "简历优化",
            "questions": [
                "如何优化我的简历？",
                "简历中应该突出哪些技能？",
                "如何写好工作经历？"
            ]
        },
        {
            "category": "面试准备", 
            "questions": [
                "面试前需要准备什么？",
                "如何回答技术问题？",
                "如何展示项目经验？"
            ]
        }
    ]
    return {"suggestions": suggestions, "timestamp": datetime.now().isoformat()}

@app.post("/api/jh/resume/upload")
async def upload_resume(file: UploadFile = File(...)):
    """上传简历文件进行分析（简易服务器版本）"""
    if not JH_DATA_AVAILABLE:
        raise HTTPException(status_code=503, detail="JH数据服务不可用")
    try:
        allowed_types = ['application/pdf', 'application/msword',
                         'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
                         'text/html', 'text/plain']
        if file.content_type not in allowed_types:
            raise HTTPException(status_code=400, detail=f"不支持的文件类型: {file.content_type}")
        content = await file.read()
        text_content = ""
        if file.content_type == 'application/pdf':
            if not PyPDF2:
                raise HTTPException(status_code=500, detail="PDF解析依赖未安装")
            text_content = _extract_text_from_pdf(content)
        elif file.content_type in ['application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document']:
            if not python_docx:
                raise HTTPException(status_code=500, detail="DOCX解析依赖未安装")
            text_content = _extract_text_from_docx(content)
        elif file.content_type == 'text/html':
            text_content = _extract_text_from_html(content)
        else:
            text_content = content.decode('utf-8', errors='ignore')
        analysis_result = await _analyze_resume_content(text_content)
        return {
            "filename": file.filename,
            "content_type": file.content_type,
            "file_size": len(content),
            "extracted_text_length": len(text_content),
            "analysis": analysis_result
        }
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"简历分析失败: {str(e)}")

@app.post("/api/jh/resume/manual")
async def analyze_manual_resume(resume_data: Dict[str, Any]):
    """分析手动输入的简历信息（简易服务器版本）"""
    if not JH_DATA_AVAILABLE:
        raise HTTPException(status_code=503, detail="JH数据服务不可用")
    try:
        text_content = f"""
        姓名: {resume_data.get('name','')}
        邮箱: {resume_data.get('email','')}
        电话: {resume_data.get('phone','')}
        教育背景: {resume_data.get('education','')}
        工作经验: {resume_data.get('experience','')}
        技能: {', '.join(resume_data.get('skills', []))}
        个人简介: {resume_data.get('summary','')}
        目标职位: {resume_data.get('target_position','')}
        期望薪资: {resume_data.get('expected_salary','')}
        """
        analysis_result = await _analyze_resume_content(text_content)
        return {"input_type": "manual", "analysis": analysis_result}
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"简历分析失败: {str(e)}")

# --- 辅助函数：提取与分析 ---

def _extract_text_from_pdf(content: bytes) -> str:
    pdf_file = io.BytesIO(content)
    reader = PyPDF2.PdfReader(pdf_file)
    text = "";
    for page in reader.pages:
        text += (page.extract_text() or "") + "\n"
    return text.strip()

def _extract_text_from_docx(content: bytes) -> str:
    file_obj = io.BytesIO(content)
    doc = python_docx.Document(file_obj)
    return "\n".join([p.text for p in doc.paragraphs]).strip()

def _extract_text_from_html(content: bytes) -> str:
    soup = BeautifulSoup(content, 'html.parser')
    for s in soup(["script", "style"]):
        s.decompose()
    return soup.get_text().strip()

async def _analyze_resume_content(text_content: str) -> Dict[str, Any]:
    # 获取技能基准（skillsChart 与数据服务对齐）
    chart_data = await job_data_service.get_chart_data("skillsChart")
    skill_matches = _analyze_skills_in_text(text_content)
    competitiveness_score = _calculate_competitiveness_score(skill_matches, text_content)
    # 修正：skill_match_score 直接取技能平均分（0-100区间），不再乘以 100
    skill_match_score = sum(skill_matches.values()) / max(len(skill_matches), 1)
    salary_competitiveness = min(skill_match_score * 1.2, 100)
    recommended_positions = await _get_recommended_positions(skill_matches)
    missing_skills = _identify_missing_skills(skill_matches)
    strength_areas = _identify_strength_areas(skill_matches)
    career_suggestions = _generate_career_suggestions(skill_matches, text_content)
    return {
        "skill_matches": skill_matches,
        "competitiveness_score": round(competitiveness_score, 1),
        "skill_match_score": round(skill_match_score, 1),
        "salary_competitiveness": round(salary_competitiveness, 1),
        "career_suggestions": career_suggestions,
        "recommended_positions": recommended_positions,
        "missing_skills": missing_skills,
        "strength_areas": strength_areas
    }

def _analyze_skills_in_text(text: str) -> Dict[str, float]:
    skills_list = [
        "python", "java", "javascript", "sql", "r", "scala", "c++", "c#",
        "machine learning", "deep learning", "data analysis", "statistics",
        "tensorflow", "pytorch", "scikit-learn", "pandas", "numpy",
        "hadoop", "spark", "kafka", "docker", "kubernetes",
        "aws", "azure", "gcp", "git", "linux", "excel"
    ]
    text_lower = text.lower()
    import random
    matches = {}
    for skill in skills_list:
        if skill in text_lower:
            count = text_lower.count(skill)
            base = min(count * 20, 80)
            matches[skill] = round(min(base * random.uniform(0.8, 1.2), 100), 1)
    return matches

def _calculate_competitiveness_score(skill_matches: Dict[str, float], text: str) -> float:
    if not skill_matches:
        return 50.0
    avg_skill = sum(skill_matches.values()) / len(skill_matches)
    exp_bonus = 10 if ("年" in text or "year" in text.lower()) else 0
    edu_bonus = 0
    for kw in ["学士", "硕士", "博士", "bachelor", "master", "phd", "university", "college"]:
        if kw.lower() in text.lower():
            edu_bonus = 5
            break
    return min(avg_skill + exp_bonus + edu_bonus, 100)

async def _get_recommended_positions(skill_matches: Dict[str, float]) -> List[Dict[str, Any]]:
    data = await job_data_service.get_jobs_paginated(page=1, page_size=80)
    jobs = data.get('jobs', [])
    recs: List[Dict[str, Any]] = []
    for job in jobs:
        job_skills = job.get('job_skills') or []
        if isinstance(job_skills, str):
            try:
                job_skills = [s.strip() for s in job_skills.split(',') if s.strip()]
            except Exception:
                job_skills = []
        matched_scores = [float(skill_matches.get(s, 0)) for s in job_skills if s]
        match_score = (sum(matched_scores) / max(len(job_skills), 1)) if job_skills else 0.0
        recs.append({
            "id": job.get("id"),
            "title": job.get('job_title_short', '') or job.get('job_title', ''),
            "company": job.get('company_name', ''),
            "location": job.get('job_location', ''),
            "match_score": round(float(match_score), 1),
            "salary_range": f"${job.get('salary_year_avg', 0):,.0f}" if job.get('salary_year_avg') else "待议",
            "type": job.get('job_schedule_type') or "全职",
            "tags": (job_skills or [])[:6],
            "url": job.get('job_posted_url') or job.get('job_apply_url') or "",
        })
    recs.sort(key=lambda x: x['match_score'], reverse=True)
    return recs[:30]

def _identify_missing_skills(skill_matches: Dict[str, float]) -> List[str]:
    important = ["Python", "SQL", "Machine Learning", "Data Analysis", "Git", "Docker"]
    return [s for s in important if s.lower() not in [k.lower() for k in skill_matches.keys()]][:5]

def _identify_strength_areas(skill_matches: Dict[str, float]) -> List[str]:
    strong = [s.lower() for s, v in skill_matches.items() if v > 70]
    areas = []
    if any(s in strong for s in ["python", "java", "javascript", "c++"]):
        areas.append("编程开发")
    if any(s in strong for s in ["machine learning", "deep learning", "tensorflow"]):
        areas.append("机器学习")
    if any(s in strong for s in ["data analysis", "statistics", "sql"]):
        areas.append("数据分析")
    if any(s in strong for s in ["aws", "azure", "docker", "kubernetes"]):
        areas.append("云计算与DevOps")
    return areas

def _generate_career_suggestions(skill_matches: Dict[str, float], text: str) -> List[str]:
    suggestions = []
    strong = [skill for skill, score in skill_matches.items() if score > 70]
    weak = [skill for skill, score in skill_matches.items() if score < 50]
    if strong:
        suggestions.append(f"您在 {', '.join(strong[:3])} 方面表现突出，建议继续深化这些技能")
    if weak:
        suggestions.append(f"建议加强 {', '.join(weak[:3])} 相关技能的学习")
    suggestions.extend([
        "考虑获得相关技术认证来提升竞争力",
        "建议参与开源项目来展示实际技能",
        "定期更新技能以跟上行业发展趋势",
        "考虑建立个人技术博客或作品集"
    ])
    return suggestions

if __name__ == "__main__":
    # Windows编码兼容性
    import sys
    if sys.platform == "win32":
        import os
        os.system("chcp 65001 >nul 2>&1")
    
    print("JH AI聊天服务器启动中...")
    print("="*50)
    if OLLAMA_AVAILABLE:
        print("Ollama服务模块加载成功")
    else:
        print("Ollama服务模块加载失败")
    if JH_DATA_AVAILABLE:
        print("JH数据服务模块加载成功")
    else:
        print("JH数据服务模块加载失败")
    print("="*50)
    
    uvicorn.run(
        app,
        host="0.0.0.0",
        port=8000,  # 使用8000端口避免冲突
        log_level="info"
    ) 